/*
 * UAE - The Un*x Amiga Emulator
 *
 * A "replacement" for a missing Kickstart
 * Warning! Q&D
 *
 * (c) 1995 Bernd Schmidt
 */

#include "sysconfig.h"
#include "ersatz.h"
#include "uae.h"
#include "gui.h"
#include "disk.h"
#include "newcpu.h"

#define EOP_INIT 0
#define EOP_NIMP 1
#define EOP_SERVEINT 2
#define EOP_DOIO 3
#define EOP_OPENLIB 4
#define EOP_AVAILMEM 5
#define EOP_ALLOCMEM 6
#define EOP_ALLOCABS 7
#define EOP_LOOP 8

static int already_failed = 0;

void init_ersatz_rom(byte* data)
{
    byte* end = data + 262144 - 16;
    /* cpu emulation uses these now */
    end[1] = 0x18;
    end[3] = 0x19;
    end[5] = 0x1a;
    end[7] = 0x1b;
    end[9] = 0x1c;
    end[11] = 0x1d;
    end[13] = 0x1e;
    end[15] = 0x1f;

    *data++ = 0x00;
    *data++ = 0x08;                 /* initial SP */
    *data++ = 0x00;
    *data++ = 0x00;
    *data++ = 0x00;
    *data++ = 0xF8;                 /* initial PC */
    *data++ = 0x00;
    *data++ = 0x08;

    *data++ = 0xFF;
    *data++ = 0x0D;
    *data++ = 0x00;
    *data++ = EOP_INIT;
    *data++ = 0xFF;
    *data++ = 0x0D;
    *data++ = 0x00;
    *data++ = EOP_NIMP;

    *data++ = 0xFF;
    *data++ = 0x0D;
    *data++ = 0x00;
    *data++ = EOP_LOOP;
    *data++ = 0xFF;
    *data++ = 0x0D;
    *data++ = 0x00;
    *data++ = EOP_DOIO;

    *data++ = 0x4E;
    *data++ = 0x75;
    *data++ = 0xFF;
    *data++ = 0x0D;
    *data++ = 0x00;
    *data++ = EOP_SERVEINT;
    *data++ = 0x4E;
    *data++ = 0x73;

    *data++ = 0xFF;
    *data++ = 0x0D;
    *data++ = 0x00;
    *data++ = EOP_AVAILMEM;
    *data++ = 0x4E;
    *data++ = 0x75;
    *data++ = 0xFF;
    *data++ = 0x0D;

    *data++ = 0x00;
    *data++ = EOP_ALLOCMEM;
    *data++ = 0x4E;
    *data++ = 0x75;
    *data++ = 0xFF;
    *data++ = 0x0D;
    *data++ = 0x00;
    *data++ = EOP_ALLOCABS;

    *data++ = 0x4E;
    *data++ = 0x75;
}

void ersatz_chipcopy()
{
    /* because CPU emulation is updated and retrieves SP and PC from chip ram */
    memcpy(chipmemory, kickmemory, 256);
}

static void ersatz_failed()
{
    if (already_failed)
        return;
    already_failed = 1;
    notify_user(NUMSG_KICKREPNO);
    uae_restart(UaeRestart_DisableNoGui, nullptr);
}

static void ersatz_doio()
{
    uae_ptr request = m68k_areg(regs, 1);
    switch (get_word(request + 0x1C))
    {
        case 9: /* TD_MOTOR is harmless */
            return;
        case 2: case 0x8002: /* READ commands */
            break;

        default:
            Logger::Write(L"Only CMD_READ supported in DoIO()\n");
            ersatz_failed();
    }
    {
        uae_ptr dest = get_long(request + 0x28);
        int start = get_long(request + 0x2C) / 512;
        int nsecs = get_long(request + 0x24) / 512;
        int tr = start / 11;
        int sec = start % 11;
        while (nsecs--)
        {
            DISK_ersatz_read(tr, sec, dest);
            dest += 512;
            if (++sec == 11)
                sec = 0, tr++;
        }
    }
}

static void ersatz_init()
{
    int f;
    uae_ptr request;
    uae_ptr a;

    already_failed = 0;
    Logger::Write(L"initializing kickstart replacement\n");
    if (disk_empty(0))
    {
        already_failed = 1;
        notify_user(NUMSG_KICKREP);
        uae_restart(UaeRestart_DisableNoGui, nullptr);
        return;
    }

    regs.s = 0;
    /* Set some interrupt vectors */
    for (a = 8; a < 0xC0; a += 4)
    {
        put_long(a, 0xF8001A);
    }
    regs.isp = regs.msp = regs.usp = 0x800;
    m68k_areg(regs, 7) = 0x80000;
    regs.intmask = 0;

    /* Build a dummy execbase */
    put_long(4, m68k_areg(regs, 6) = 0x676);
    put_byte(0x676 + 0x129, 0);
    for (f = 1; f < 105; f++)
    {
        put_word(0x676 - 6 * f, 0x4EF9);
        put_long(0x676 - 6 * f + 2, 0xF8000C);
    }
    /* Some "supported" functions */
    put_long(0x676 - 456 + 2, 0xF80014);
    put_long(0x676 - 216 + 2, 0xF80020);
    put_long(0x676 - 198 + 2, 0xF80026);
    put_long(0x676 - 204 + 2, 0xF8002c);
    put_long(0x676 - 210 + 2, 0xF8002a);

    /* Build an IORequest */
    request = 0x800;
    put_word(request + 0x1C, 2);
    put_long(request + 0x28, 0x4000);
    put_long(request + 0x2C, 0);
    put_long(request + 0x24, 0x200 * 4);
    m68k_areg(regs, 1) = request;
    ersatz_doio();
    /* kickstart disk loader */
    if (get_long(0x4000) == 0x4b49434b)
    {
        /* a kickstart disk was found in drive 0! */
        Logger::Write(L"Loading Kickstart rom image from Kickstart disk\n");
        /* print some notes... */
        Logger::Write(L"NOTE: if UAE crashes set CPU to 68000 and/or chipmem size to 512KB!\n");

        /* read rom image from kickstart disk */
        put_word(request + 0x1C, 2);
        put_long(request + 0x28, 0xF80000);
        put_long(request + 0x2C, 0x200);
        put_long(request + 0x24, 0x200 * 512);
        m68k_areg(regs, 1) = request;
        ersatz_doio();

        /* read rom image once again to mirror address space.
           not elegant, but it works... */
        put_word(request + 0x1C, 2);
        put_long(request + 0x28, 0xFC0000);
        put_long(request + 0x2C, 0x200);
        put_long(request + 0x24, 0x200 * 512);
        m68k_areg(regs, 1) = request;
        ersatz_doio();

        disk_eject(0);

        m68k_setpc(0xFC0002);
        fill_prefetch();
        uae_reset(0);
        ersatzkickfile = 0;
        return;
    }

    m68k_setpc(0x400C);
    fill_prefetch();

    /* Init the hardware */
    put_long(0x3000, 0xFFFFFFFEul);
    put_long(0xDFF080, 0x3000);
    put_word(0xDFF088, 0);
    put_word(0xDFF096, 0xE390);
    put_word(0xDFF09A, 0xE02C);
    put_word(0xDFF09E, 0x0000);
    put_word(0xDFF092, 0x0038);
    put_word(0xDFF094, 0x00D0);
    put_word(0xDFF08E, 0x2C81);
    put_word(0xDFF090, 0xF4C1);
    put_word(0xDFF02A, 0x8000);

    put_byte(0xBFD100, 0xF7);
    put_byte(0xBFEE01, 0);
    put_byte(0xBFEF01, 0x08);
    put_byte(0xBFDE00, 0x04);
    put_byte(0xBFDF00, 0x84);
    put_byte(0xBFDD00, 0x9F);
    put_byte(0xBFED01, 0x9F);
}

void ersatz_perform(ushort what)
{
    switch (what)
    {
        case EOP_INIT:
            ersatz_init();
            break;

        case EOP_SERVEINT:
        {
            ushort intreq = get_word(0xDFF01E);
            /* Just reset all the interrupt request bits */
            if (intreq & 0x0008)
                get_byte(0xbfed01);  /* possible keyboard interrupt */
            put_word(0xDFF09C, intreq & 0x3FFF);
            break;
        }

        case EOP_DOIO:
            ersatz_doio();
            break;

        case EOP_AVAILMEM:
            m68k_dreg(regs, 0) = m68k_dreg(regs, 1) & 4 ? 0 : 0x70000;
            break;

        case EOP_ALLOCMEM:
            m68k_dreg(regs, 0) = m68k_dreg(regs, 1) & 4 ? 0 : 0x0F000;
            break;

        case EOP_ALLOCABS:
            m68k_dreg(regs, 0) = m68k_areg(regs, 1);
            break;

        case EOP_NIMP:
            Logger::Write(L"Unimplemented Kickstart function called\n");
            ersatz_failed();

        /* fall through */
        case EOP_LOOP:
            m68k_setpc(0xF80010);
            break;

        case EOP_OPENLIB:
        default:
            Logger::Write(L"Internal error. Giving up.\n");
            ersatz_failed();
    }
}